package se.kayarr.cardplay.gameplay;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import lombok.Getter;

import org.pircbotx.PircBotX;

import se.kayarr.cardplay.exceptions.PlayerUnspecifiedException;
import se.kayarr.cardplay.exceptions.PositionOutsideFieldException;
import se.kayarr.cardplay.exceptions.SessionInProgressException;
import se.kayarr.cardplay.exceptions.TooManyPlayersException;
import se.kayarr.cardplay.gameplay.UnitCard.Instance;
import se.kayarr.cardplay.shared.StringReferenceParser;
import se.kayarr.cardplay.shared.StringReferenceParser.NoSuchPlayerException;

public class Session {
	@Getter private static Session currentSession;
	
	private static int identCounter = 1;
	
	public static Session newSession(PircBotX bot) throws SessionInProgressException {
		if(currentSession != null) throw new SessionInProgressException();
		return (currentSession = new Session(bot));
	}
	
	public static void removeSession() {
		currentSession = null;
	}
	
	public static Session getSessionbyId(String identifier) {
		//There can only be one session at the time for now so just return that if the session ID matches
		//TODO When this supports multiple sessions, return the right session
		return (currentSession.identifier.equals(identifier)) ? currentSession : null;
	}
	
	@Getter private String identifier;
	
	private List<Player> players = new ArrayList<>();
	private int currentPlayer = -1;
	
	private Set<String> narrateTargets = new HashSet<>();
	private Map<String, String> options = new HashMap<>();
	
	@Getter private boolean inPlay = false;
	
	@Getter private int round = 1;
	
	@Getter private boolean finished = false;
	
	private PircBotX bot;
	
	private Session(PircBotX bot) {
		this.bot = bot;
		
		this.identifier = "&" + (identCounter++);
	}
	
	public void addPlayer(Player player) {
		checkThisSessionInPlay();
		players.add(player);
	}
	
	public List<Player> getPlayers() {
		return Collections.unmodifiableList(players);
	}
	
	public Player getPlayerByDisplayedTitle(String displayedTitle) {
		for(Player player : players) {
			if(player.getName().equalsIgnoreCase(displayedTitle)) return player;
		}
		return null;
	}
	
	public Player getOppositePlayer(Player player) throws TooManyPlayersException {
		if(players.size() > 2) throw new TooManyPlayersException();
		
		return players.get(0) == player ? players.get(1) : players.get(0);
	}
	
	public Player getCurrentPlayer() {
		return players.get(currentPlayer);
	}
	
	public Targetable getTargetByString(Player thisPlayer, String refString, int targetFlags)
			throws NoSuchPlayerException, TooManyPlayersException {
		
		if((targetFlags & Targetable.ANY_UNIT) != 0) {
			Card.Instance instance;
			try {
				instance = StringReferenceParser.getReferencedInstance(refString, this, thisPlayer);
			}
			catch (PlayerUnspecifiedException e) {
				return null;
			}
			catch (PositionOutsideFieldException e) {
				return null;
			}
			
			if(instance != null) {
				if(instance instanceof UnitCard.Instance) {
					UnitCard.Instance unit = (Instance) instance;
					
					if((targetFlags & Targetable.ALLY_UNIT) != 0 && unit.getOwner() == thisPlayer) return unit;
					if((targetFlags & Targetable.OPPONENT_UNIT) != 0 && unit.getOwner() != thisPlayer) return unit;
				}
				else return null;
			}
		}
		
		if((targetFlags & Targetable.ANY_PLAYER) != 0) {
			Player player;
			
			if(refString.equals("@")) {
				player = getOppositePlayer(thisPlayer);
			}
			else player = getPlayerByDisplayedTitle(refString);
			
			if((targetFlags & Targetable.ALLY_PLAYER) != 0 && player == thisPlayer) return player;
			if((targetFlags & Targetable.OPPONENT_PLAYER) != 0 && player != thisPlayer) return player;
		}
		
		return null;
	}
	
	public static void checkSessionInProgress() throws SessionInProgressException {
		if(Session.getCurrentSession() != null) throw new SessionInProgressException();
	}
	
	protected void checkThisSessionInPlay() {
		if(inPlay) throw new IllegalStateException("Session is currently in play");
	}
	
	public void setOption(String key, String value) {
		checkThisSessionInPlay();
		options.put(key, value);
	}
	
	public String getOption(String key) {
		return options.get(key);
	}
	
	public void addNarrateTarget(String target) {
		checkThisSessionInPlay();
		
		narrateTargets.add(target);
	}
	
	public void narrate(String message) {
		System.out.println("NARRATION: " + message);
		for(String target : narrateTargets) {
			bot.sendMessage(target, message);
		}
	}
	
	public void startSession() {
		checkThisSessionInPlay();
		
		for(Player player : players) {
			player.prepare();
		}
		
		inPlay = true;
		onRoundStart();
		progressTurn(true);
	}
	
	void progressTurn() {
		if(finished) return;
		
		progressTurn(false);
	}
	
	protected void progressTurn(boolean firstTurn) {
		if(!firstTurn) onTurnEnd();
		
		if(finished) return;
		
		currentPlayer = (++currentPlayer) % players.size();
		if(currentPlayer == 0 && !firstTurn) {
			onRoundEnd();
			round++;
			onRoundStart();
		}
		onTurnStart();
		
		Player player = players.get(currentPlayer);
		narrate("It is now " + player.getName() + "'s turn!");
		player.showCards();
	}
	
	protected void onRoundStart() {
		System.out.println("onRoundStart");
		narrate("** Round #"+round+" Statistics:");
		outputStatus();
	}
	
	protected void onRoundEnd() {
		narrate("** End of round #" + round + "!");
		System.out.println("onRoundEnd");
	}
	
	protected void onTurnStart() {
		System.out.println("onTurnStart");
	}
	
	protected void onTurnEnd() {
		cleanupDead();
		getCurrentPlayer().clearUsedUnits();
		
		for(Player player : players) {
			if(player.getHealth().isAtMin()) {
				try {
					narrate(player.getName() + " is dead, " + getOppositePlayer(player).getName() + " is winrar !!!!!!");
					Session.removeSession();
				}
				catch (TooManyPlayersException e) {}
			}
		}
		
		System.out.println("onTurnEnd");
	}
	
	void outputStatus() {
		for(Player player : players) {
			narrate(player.toString());
			for(int i = 0; i < player.getField().getHeight(); i++) {
				narrate((i+1) + " " + player.getField().outputRow(i));
			}
		}
	}
	
	void cleanupDead() {
		Set<Player> deadPlayers = new HashSet<>();
		
		for(Player player : players) {
			player.getField().cleanupDeadUnits();
			
			if(player.isAlive() && player.getHealth().isAtMin()) {
				player.onDeath();
				deadPlayers.add(player);
			}
		}
		
		players.removeAll(deadPlayers);
		
		if(players.size() == 1) {
			Player winner = players.get(0);
			
			narrate(winner.getName() + " has won this session!!!!");
			
			endSession();
		}
		else if(players.size() == 0) {
			narrate("...W-well uh, this... I... dang. Looks like nobody won. Dang.");
			
			endSession();
		}
	}
	
	private void endSession() {
		if(finished) return;
		
		inPlay = false;
		finished = true;
		
		players.clear();
		narrateTargets.clear();
		options.clear();
		
		Session.removeSession();
	}
}
